package modules.dimensioning;

import java.util.Random;
import java.util.SortedSet;
import java.util.TreeSet;

import javax.naming.directory.InvalidAttributesException;

import modules.dimensioning.solution.PartialSolution;
import modules.dimensioning.solution.Solution;
import tools.Log;

import com.vividsolutions.jts.geom.Geometry;

import datatypes.Antenna;

/**
 * Basisheuristiek, basis van genetic search.
 * 
 * @author Bart Moens
 */
public class GeneticSearch {
	
	private Solution[] solutionArray;
	
	/**
	 * Constructor 
	 * 
	 * @param antennas Antenna[]
	 * @param ci CoverageInterface
	 * @param li LegislationInterface
	 * @param cc CoverageConstraint
	 * @param lc LegislationInterface
	 * @param population
	 * @throws InvalidAttributesException
	 */
	public GeneticSearch(Antenna[] antennas, Geometry coverageArea, 
			CoverageInterface ci, LegislationInterface li, 
			int population) throws InvalidAttributesException {
		try {
			Log.add(this,"Loading Genetic Search");
			solutionArray = new Solution[population];
			Log.add(this,"Creating solution population");
			for (int i = 0; i < solutionArray.length; i++)
				solutionArray[i] = new Solution(antennas, coverageArea, li,ci);

			//write areas
			Geometry[] g = new Geometry[solutionArray[0].getPartialSolutions().length +1];
			for (int i = 0; i<solutionArray[0].getPartialSolutions().length ; i++){
				g[i] = solutionArray[0].getPartialSolutions()[i].getAreaToCover();
			}
			g[solutionArray[0].getPartialSolutions().length] = coverageArea;	
		}
		catch (Exception e) {
			e.printStackTrace();
			System.exit(1);
		}
	}

	/**
	 * Returns a sorted set of the solutions
	 * 
	 * @return SortedSet
	 */
	public SortedSet<Solution> getSolutions() {
		SortedSet<Solution> set = new TreeSet<Solution>();
		for (int k = 0; k < solutionArray.length; k++) {
			set.add(solutionArray[k].clone());			
		}
		return set;
	}
	
	/**
	 * Starts the genetic algorithm
	 * 
	 * @param generations How many generations must be searched
	 * @param timeInMs    maximum allowed time
	 */
	public void runGeneticAlgorithm(int generations, long timeInMs) {
		printSolutionArray(0);
		
		Log.add(this,"Running search...");
		
		long generationTime = System.currentTimeMillis();
		long startTime = System.currentTimeMillis();
		//for each generation
		int generation;
		for (generation = 1; generation < generations && (System.currentTimeMillis()-startTime <timeInMs); generation++) {
			try { Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}
			
			if (generation % 50 != 0) {
				// Mutatie
				for (int j = 0; j < solutionArray.length; j++) {
					solutionArray[j].mutateAll();
					//solutionArray[j].disableHalf();
				}
			} else {
				System.out.println();				
				int natsel = 1+(int)(solutionArray.length)/10;  //+- 10 %
				Log.add(this,"doNaturalSelectionAndCrossOver ("+natsel+")");
				doNaturalSelectionAndCrossOver(natsel);
				System.out.println();				
			}
			
			// print debug info after 50 generations
			if (generation % 50 == 0) {
				System.out.println();				
				printSolutionArray(generation);
				Log.add(this,"Timing: ");			
				Log.add(this,"\t" + "Time tot: " + ((int)((System.currentTimeMillis() - generationTime)))+ " ms ");
				Log.add(this,"\t" + "Time/ite: " + ((int)((System.currentTimeMillis() - generationTime)/50))+ " ms");
				Log.add(this,"\t" + "Time/sol: " + ((int)((System.currentTimeMillis() - generationTime)/(50*solutionArray.length)))+ " ms");
				try { Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}
				generationTime = System.currentTimeMillis();
				Log.add(this, "Time left: " + (int)((timeInMs-(System.currentTimeMillis()-startTime))/1000) + " sec");
			} else {
				System.out.print(".");
			}
			
		}
		System.out.println();				
		printSolutionArray(generation);
		
	}

	/**
	 * Natural selection
	 * @param times How many weak elements should be removed
	 */
	private void doNaturalSelectionAndCrossOver(int times) {
			try {
				SortedSet<Double> set = new TreeSet<Double>();
				for (int i = 0; i < solutionArray.length; i++)
					set.add(solutionArray[i].getFitnessValue());
				Double[] tmp = set.toArray(new Double[0]);
				
				for (int i = 1; i < times; i++) {
					int worst = -1;
					double worstFitness = tmp[i-1];
					
					for (int j = 0; j < solutionArray.length; j++) {
						if (solutionArray[j].getFitnessValue() == worstFitness){
							worst = j;
						}
					}
					solutionArray[worst] = crossOver(solutionArray[worst].clone(), i==1);
				}
			} catch (Exception e) {
                            e.printStackTrace();
                        }
	}

	/**
	 * Do a crossover with base s
	 * @param s base solution
	 * @param doBest Search the best possible partialSolutions for crossing.  No random factor involved if this is the case.
	 * @return
	 */
	private Solution crossOver(Solution s, boolean doBest) {
		PartialSolution[] partialSolutions = s.getPartialSolutions();
		
		if (doBest) {
			//generate best child, not most diverse
			for (int i = 0; i < partialSolutions.length; i++) {
				for (int j = 0; j < solutionArray.length; j++) {
					if (partialSolutions[i].getFitnessValue()< solutionArray[j].getPartialSolutions()[i].getFitnessValue()) {
						partialSolutions[i] = solutionArray[j].getPartialSolutions()[i].clone();
					}
				}
			}
		} else {
			//add a certain random factor
			Random r=new Random();
			for (int i = 0; i < partialSolutions.length; i++) {
				for (int j = 0; j < solutionArray.length/2; j++) {
					int sol = r.nextInt(solutionArray.length);
					if (partialSolutions[i].getFitnessValue()< solutionArray[sol].getPartialSolutions()[i].getFitnessValue()) {
						partialSolutions[i] = solutionArray[sol].getPartialSolutions()[i].clone();
					}
				}
			}
		}
		s.setPartialSolutions(partialSolutions);
		return s;
	}
	

	private void printSolutionArray(int iteration) {
		Log.add(this,"Generation: " + iteration);
		double avgCov = 0;
		double avgCos = 0;
		double avgFit1 = 0;

		SortedSet<Solution> set = new TreeSet<Solution>();
		for (int k = 0; k < solutionArray.length; k++) {
			set.add(solutionArray[k]);			
		}
		
		int k = 0;
		for (Solution s : set) {
			Log.add(this,"[GE"+iteration+"]: [S"+k++ +"]: "+ s);
			avgCov += s.getCoverageFitnessValue();
			avgCos += s.getCostFitnessValue();
			avgFit1 += s.getFitnessValue();
		}
		avgCov /= set.size();
		avgCos /= set.size();
		avgFit1 /= set.size();

		Log.add(this,"Generation " + iteration + ": ");
		Log.add(this,"\t" + "(MAX) Avg Fitness               [0-600] : " + avgFit1 + "");
		Log.add(this,"\t" + "(MAX) Avg Coverage             [0-100%]: " + avgCov + "%");
		Log.add(this,"\t" + "(MIN) Avg Cost (higher=better)  [0-100%]: " + avgCos + "%");
		
	}

}
